﻿// ------------------------------------------------------------------------
//  Copyright 2025 The Dapr Authors
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//      http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//  ------------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;
using Dapr.Cryptography.Encryption.Models;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;

namespace Dapr.Cryptography.Encryption;

/// <summary>
/// <para>
/// Defines client operations for performing cryptography operations with Dapr..
/// Use <see cref="DaprEncryptionClientBuilder"/> to create a <see cref="DaprEncryptionClient"/> or register
/// for use with dependency injection via
/// <see><cref>DaprJobsServiceCollectionExtensions.AddDaprJobsClient</cref></see>.
/// </para>
/// <para>
/// Implementations of <see cref="DaprEncryptionClient"/> implement <see cref="IDisposable"/> because the
/// client accesses network resources. For best performance, create a single long-lived client instance
/// and share it for the lifetime of the application. This is done for you if created via the DI extensions. Avoid
/// creating a disposing a client instance for each operation that the application performs - this can lead to socket
/// exhaustion and other problems.
/// </para>
/// </summary>
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprEncryptionClient
{
    private bool disposed;
    
    /// <summary>
    /// The HTTP client used by the client for calling the Dapr runtime.
    /// </summary>
    /// <remarks>
    /// Property exposed for testing purposes.
    /// </remarks>
    internal readonly HttpClient HttpClient = httpClient;

    /// <summary>
    /// The Dapr API token value.
    /// </summary>
    /// <remarks>
    /// Property exposed for testing purposes.
    /// </remarks>
    internal readonly string? DaprApiToken = daprApiToken;

    /// <summary>
    /// The autogenerated Dapr client.
    /// </summary>
    /// <remarks>
    /// Property exposed for testing purposes.
    /// </remarks>
    internal Autogenerated.DaprClient Client { get; } = client;
    
    /// <summary>
    /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality.
    /// </summary>
    /// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
    /// <param name="plaintextBytes">The bytes of the plaintext value to encrypt.</param>
    /// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
    /// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
    /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
    /// <returns>An array of encrypted bytes.</returns>
    public abstract Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
        ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Encrypts a stream using the Dapr Cryptography encryption functionality.
    /// </summary>
    /// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
    /// <param name="plaintextStream">The stream containing the bytes of the plaintext value to encrypt.</param>
    /// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
    /// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
    /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
    /// <returns>An array of encrypted bytes.</returns>
    public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
        EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);

    /// <summary>
    /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality.
    /// </summary>
    /// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
    /// <param name="ciphertextBytes">The bytes of the ciphertext value to decrypt.</param>
    /// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
    /// <param name="options">Options informing how the decryption operation should be configured.</param>
    /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
    /// <returns>An array of decrypted bytes.</returns>
    public abstract Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions? options = null,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality.
    /// </summary>
    /// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
    /// <param name="ciphertextStream">The stream containing the bytes of the ciphertext value to decrypt.</param>
    /// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
    /// <param name="options">Options informing how the decryption operation should be configured.</param>
    /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
    /// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
    public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
        string keyName, DecryptionOptions? options = null, CancellationToken cancellationToken = default);
    
    internal static KeyValuePair<string, string>? GetDaprApiTokenHeader(string apiToken)
    {
        if (string.IsNullOrWhiteSpace(apiToken))
        {
            return null;
        }

        return new KeyValuePair<string, string>("dapr-api-token", apiToken);
    }
    
    /// <inheritdoc />
    public void Dispose()
    {
        if (!this.disposed)
        {
            Dispose(disposing: true);
            this.disposed = true;
        }
    }

    /// <summary>
    /// Disposes the resources associated with the object.
    /// </summary>
    /// <param name="disposing"><c>true</c> if called by a call to the <c>Dispose</c> method; otherwise false.</param>
    protected virtual void Dispose(bool disposing)
    {
    }
}
